package currency;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

abstract class Accumulator {
    public static long sCycles = 50000L;

    // Number of Modifiers and Readers during each test
    private static final int N = 4;

    public static ExecutorService sExec = Executors.newFixedThreadPool(N * 2);

    private static CyclicBarrier sBarrier = new CyclicBarrier(N * 2 + 1);

    protected long mDuration = 0;

    protected String mId = "error";

    protected volatile long mValue = 0;

    protected volatile int mIndex = 0;

    protected final static int SIZE = 100000;

    protected static int[] sPreLoaded = new int[SIZE];

    static {
        // Load the array of random numbers
        Random rand = new Random(47);
        for(int i = 0; i < SIZE; i++)
            sPreLoaded[i] = rand.nextInt();
    }

    public void timedTest() {
        long startTime = System.nanoTime();

        for(int i = 0; i < N; i++) {
            sExec.execute(new Modifier());
            sExec.execute(new Reader());
        }

        try {
            sBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }

        mDuration = System.nanoTime() - startTime;

        System.out.println(String.format("%-13s: %13d", mId, mDuration));
    }

    public abstract void accumulate();

    public abstract long read();

    public static void report(Accumulator acc1, Accumulator acc2) {
        String s = String.format("%-22s: %.2f", acc1.mId + "/" + acc2.mId,
                                 (double)acc1.mDuration / (double)acc2.mDuration);
        System.out.println(s);
    }

    private class Modifier implements Runnable {
        @Override
        public void run() {
            for(long i = 0; i < sCycles; i++) {
                accumulate();
            }
            try {
                sBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    private class Reader implements Runnable {
        @Override
        public void run() {
            for(long i = 0; i < sCycles; i++) {
                mValue = read();
            }
            try {
                sBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}
